package org.jfsm.demo.calculator;

import org.jfsm.core.FSMException;
import org.jfsm.core.FSMInput;
import org.jfsm.core.FSMOutput;
import org.jfsm.core.RetSignal;
import org.jfsm.core.SimpleFSM;
import org.jfsm.core.SimpleFSMContext;

class CalcController {

    static public class CalcContext extends SimpleFSMContext {
        int num1, display;
        char op;

        void calculate() {
            switch (op) {
            case '+':
                num1 += display;
                break;
            case '-':
                num1 -= display;
                break;
            case '*':
                num1 *= display;
                break;
            case '/':
                num1 /= display;
                break;
            default:

            }
            display = num1;
        }

        @Override
        public String toString() {
            return "( num1:" + num1 + ", display:" + display + ", op:" + op + " )";
        }
    }

    static public class CalcInputs extends FSMInput<CalcContext> {
        SignalM<Character> key, digit, op;
        Signal backspace, equals;
    };

    private enum State {
        Init, Num1, Op, Num2
    }

    static class CalcFSM extends SimpleFSM<CalcContext, CalcInputs, FSMOutput> {

        @Override
        protected void build(TransitionMap tm) {
            tm.setInitialState(State.Init);

            tm.putDefault(in.key, null, mhKey);

            tm.put(State.Init, in.digit, State.Num1, mhFirstDigit);
            tm.put(State.Init, in.backspace, State.Init, ehClear);
            tm.put(State.Init, in.op, State.Op, mhOp);
            tm.put(State.Init, in.equals, State.Init, null);

            tm.put(State.Num1, in.digit, State.Num1, mhNumAppend);
            tm.put(State.Num1, in.backspace, State.Num1, ehNumBack);
            tm.put(State.Num1, in.op, State.Op, mhOp);
            tm.put(State.Num1, in.equals, State.Init, null);

            tm.put(State.Op, in.digit, State.Num2, mhFirstDigit);
            tm.put(State.Op, in.backspace, State.Op, ehClear);
            tm.put(State.Op, in.op, State.Op, mhOp);
            tm.put(State.Op, in.equals, State.Init, ehEquals);

            tm.put(State.Num2, in.digit, State.Num2, mhNumAppend);
            tm.put(State.Num2, in.backspace, State.Op, ehNumBack);
            tm.put(State.Num2, in.op, State.Op, mhOpOnNum2);
            tm.put(State.Num2, in.equals, State.Init, ehEquals);
        }

        MH<Character> mhKey = new MH<Character>() {
            public RetSignal handle(CalcContext context, Character key) {

                if (key >= '0' && key <= '9') {
                    return in.digit.asNextSignal(key);
                } else if (key == '+' || key == '-' || key == '*' || key == '/') {
                    return in.op.asNextSignal(key);
                } else if (key == '=')
                    return in.equals;
                else if (key == 'D')
                    return in.backspace;
                else
                    return null;
            }
        };

        EH ehClear = new EH() {
            public RetSignal handle(CalcContext ctx) {
                ctx.display = 0;
                return null;
            }
        };

        MH<Character> mhFirstDigit = new MH<Character>() {
            public RetSignal handle(CalcContext ctx, Character key) {
                ctx.display = 0;
                return in.digit.asNextSignal(key);
            }
        };

        MH<Character> mhNumAppend = new MH<Character>() {
            public RetSignal handle(CalcContext ctx, Character key) {
                ctx.display = ctx.display * 10 + (key - '0');
                return null;
            }
        };

        EH ehNumBack = new EH() {
            public RetSignal handle(CalcContext ctx) {
                ctx.display /= 10;
                return null;
            }
        };

        MH<Character> mhOp = new MH<Character>() {
            public RetSignal handle(CalcContext ctx, Character key) {
                ctx.op = key;
                ctx.num1 = ctx.display;
                return null;
            }
        };

        MH<Character> mhOpOnNum2 = new MH<Character>() {
            public RetSignal handle(CalcContext ctx, Character key) {
                ctx.calculate();
                ctx.op = key;
                return null;
            }
        };

        EH ehEquals = new EH() {
            public RetSignal handle(CalcContext ctx) {
                ctx.calculate();
                return null;
            }
        };

    }

    CalcInputs dispatcher;
    CalcContext ctx;

    public CalcController() throws FSMException {
        dispatcher = new CalcFSM().createInstance().getInput();
        ctx = dispatcher.newContext();
    }

}
